1
|
|
|
var Objects = require('../Utility').Objects |
2
|
|
|
var TriggerType = require('./TriggerType').TriggerType |
3
|
|
|
|
4
|
|
|
var severityFactory = function (id, weight) { |
5
|
|
|
return { |
6
|
|
|
id: id, |
7
|
|
|
weight: weight |
8
|
|
|
} |
9
|
|
|
} |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* @enum |
13
|
|
|
* @readonly |
14
|
|
|
*/ |
15
|
|
|
var Severity = { |
16
|
|
|
None: severityFactory('None', 0), |
17
|
|
|
Minor: severityFactory('Minor', 1), |
18
|
|
|
Major: severityFactory('Major', 2), |
19
|
|
|
Fatal: severityFactory('Fatal', 3) |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @typedef {object} TViolation |
24
|
|
|
* |
25
|
|
|
* @property {string} message |
26
|
|
|
* @property {Severity} severity |
27
|
|
|
*/ |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* A simple wrapper for list of violations, grouped by path. |
31
|
|
|
* |
32
|
|
|
* @class |
33
|
|
|
* |
34
|
|
|
* @property {Object.<string, TViolation[]>} violations Violations grouped by |
35
|
|
|
* path |
36
|
|
|
* @property {Severity} severity |
37
|
|
|
*/ |
38
|
|
|
function ViolationSet () { |
39
|
|
|
var self = this |
40
|
|
|
var violations = {} |
41
|
|
|
var severity = Severity.None |
42
|
|
|
|
43
|
|
|
function updateSeverity (l) { |
44
|
|
|
severity = l.weight > severity.weight ? l : severity |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @param {string} path |
49
|
|
|
* @param {string} message |
50
|
|
|
* @param {Severity} severity |
51
|
|
|
* @return {ViolationSet} |
52
|
|
|
*/ |
53
|
|
|
this.push = function (path, message, severity) { |
54
|
|
|
violations[path] = violations[path] ? violations[path] : [] |
55
|
|
|
violations[path].push({message: message, severity: severity}) |
56
|
|
|
updateSeverity(severity) |
57
|
|
|
return self |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
this.merge = function (other) { |
61
|
|
|
Object.keys(other.violations).forEach(function (path) { |
62
|
|
|
other.violations[path].forEach(function (violation) { |
63
|
|
|
self.push(path, violation.message, violation.severity) |
64
|
|
|
}) |
65
|
|
|
}) |
66
|
|
|
return self |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
Object.defineProperties(this, { |
70
|
|
|
severity: {get: function () { return severity }}, |
71
|
|
|
violations: {get: function () { return violations }} |
72
|
|
|
}) |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
var Validator = { |
76
|
|
|
Severity: Severity, |
77
|
|
|
ViolationSet: ViolationSet, |
78
|
|
|
/** |
79
|
|
|
* @param {TScenarioInput} input |
80
|
|
|
* |
81
|
|
|
* @return ViolationSet |
82
|
|
|
*/ |
83
|
|
|
scenario: function (input) { |
84
|
|
|
var violations = new ViolationSet() |
85
|
|
|
var violation |
86
|
|
|
var handlers = ['deserializer', 'onError', 'onTermination'] |
87
|
|
|
handlers.forEach(function (handler) { |
88
|
|
|
var path = '$.' + handler |
89
|
|
|
if (!input[handler]) { |
90
|
|
|
var message = handler + ' handler is missing, default will be used' |
91
|
|
|
violations.push(path, message, Severity.Minor) |
92
|
|
|
} else { |
93
|
|
|
violations.merge(Validator.handler(input[handler], path)) |
94
|
|
|
} |
95
|
|
|
}) |
96
|
|
|
if (!TriggerType.find(input.trigger)) { |
97
|
|
|
violation = 'Scenario trigger type is not set or invalid' |
98
|
|
|
violations.push('$.trigger', violation, Severity.Fatal) |
99
|
|
|
} |
100
|
|
|
var states = input.states |
101
|
|
|
if (!Objects.isObject(states)) { |
102
|
|
|
violations.push('$.states', 'Expected to be an object', Severity.Fatal) |
103
|
|
|
states = {} |
104
|
|
|
} |
105
|
|
|
var terminalStates = 0 |
106
|
|
|
var entrypointStates = 0 |
107
|
|
|
Object.keys(states).forEach(function (id) { |
108
|
|
|
var state = states[id] |
109
|
|
|
violations.merge(Validator.state(state, '$.states.' + id)) |
110
|
|
|
entrypointStates += state && state.entrypoint ? 1 : 0 |
111
|
|
|
terminalStates += state && state.terminal ? 1 : 0 |
112
|
|
|
}) |
113
|
|
|
if (terminalStates === 0) { |
114
|
|
|
violations.push('$.states', 'No terminal state is defined', Severity.Fatal) |
115
|
|
|
} |
116
|
|
|
if (entrypointStates !== 1) { |
117
|
|
|
violation = entrypointStates + ' entrypoint states are defined ' + |
118
|
|
|
'(exactly one expected)' |
119
|
|
|
violations.push('$.states', violation, Severity.Fatal) |
120
|
|
|
} |
121
|
|
|
return violations |
122
|
|
|
}, |
123
|
|
|
/** |
124
|
|
|
* @param {TStateInput} state |
125
|
|
|
* @param {string} [path] |
126
|
|
|
* |
127
|
|
|
* @return {ViolationSet} |
128
|
|
|
*/ |
129
|
|
|
state: function (state, path) { |
130
|
|
|
path = path || '$' |
131
|
|
|
var violations = new ViolationSet() |
132
|
|
|
if (Objects.isFunction(state)) { |
133
|
|
|
return violations |
134
|
|
|
} |
135
|
|
|
if (!Objects.isObject(state)) { |
136
|
|
|
return violations.push(path, 'Is not an object', Severity.Fatal) |
137
|
|
|
} |
138
|
|
|
var handlers = {transition: Severity.Fatal, abort: Severity.Minor} |
139
|
|
|
Object.keys(handlers).forEach(function (key) { |
140
|
|
|
var prop = path + '.' + key |
141
|
|
|
if (!state[key]) { |
142
|
|
|
violations.push(prop, key + ' handler is missing', handlers[key]) |
143
|
|
|
} else { |
144
|
|
|
violations.merge(Validator.handler(state[key], prop)) |
145
|
|
|
} |
146
|
|
|
}) |
147
|
|
|
return violations |
148
|
|
|
}, |
149
|
|
|
handler: function (handler, path) { |
150
|
|
|
path = path || '$' |
151
|
|
|
var violations = new ViolationSet() |
152
|
|
|
if (!handler) { |
153
|
|
|
return violations |
154
|
|
|
} |
155
|
|
|
var callable = handler |
156
|
|
|
if (Objects.isObject(handler)) { |
157
|
|
|
violations.merge(Validator.handler(handler.onTimeout, path + '.onTimeout')) |
158
|
|
|
callable = handler.handler |
159
|
|
|
} |
160
|
|
|
if (!Objects.isFunction(callable)) { |
161
|
|
|
var message = 'Neither handler itself or handler.handler is a function' |
162
|
|
|
violations.push(path, message, Severity.Fatal) |
163
|
|
|
} |
164
|
|
|
return violations |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
module.exports = { |
169
|
|
|
Validator: Validator |
170
|
|
|
} |
171
|
|
|
|